/**
 * ZHU framework. Copyright 2012-, all rights reserved.
 *
 * $Id: LifecycleFactory.java 24 2012-08-17 07:21:26Z zhuxiaopeng $
 * $Revision: 24 $
 * $Date: 2012-08-17 15:21:26 +0800 (五, 2012-08-17) $
 * $Author: zhuxiaopeng $
 */
package zhu.framework.entity.lifecycle;

import java.util.*;
import java.util.logging.*;

import javax.persistence.*;

import zhu.framework.entity.Entity;
import zhu.framework.util.*;

/**
 * <p>
 * </p>
 * 
 * @author $Author: zhuxiaopeng $
 * @version $Revision: 24 $ - $Date: 2012-08-17 15:21:26 +0800 (五, 2012-08-17) $
 */
public final class LifecycleFactory {
	static private Logger getLogger() {
		final LoggerManager manager = LoggerManager.getInstance();
		final Logger logger = manager.getLogger(LifecycleFactory.class);
		return logger;
	}

	private static final Class<?>[] LIFECYCLE_IMPL_CONSTRUCTOR_SIGNATURE = new Class[] { LifecycleContext.class };

	public LifecycleFactory(final EntityManager entityManager) {
		MyUtility.checkMethodParameterNotNull("entityManager", entityManager);
		this.lifecycleContext = new LifecycleContext(entityManager, this);
	}

	public <T extends Entity> Lifecycle<T> getLifecycleFor(final Class<T> entityClass) {
		MyUtility.checkMethodParameterNotNull("entityClass", entityClass);
		final Class<Lifecycle<T>> lifecycleClass = getLifecycleClassFor(entityClass);
		return getLifecycle(lifecycleClass);
	}

	public <T extends Lifecycle<?>> T getLifecycle(final Class<T> lifecycleClass) {
		MyUtility.checkMethodParameterNotNull("lifecycleClass", lifecycleClass);
		final Lifecycle<?> lifecycle = getInstanceCache().get(lifecycleClass);
		if (lifecycle == null) {
			final T newInstance = ReflectionUtility.newInstance(lifecycleClass, LIFECYCLE_IMPL_CONSTRUCTOR_SIGNATURE, getLifecycleContext());
			if (newInstance != null) {
				getInstanceCache().put(lifecycleClass, newInstance);
				return newInstance;
			} else {
				final LifecycleException e = new LifecycleException("ReflectionUtility#newInstance() returned 'null'");
				throw e;
			}
		}
		return lifecycleClass.cast(lifecycle);
	}

	private <T extends Entity> Class<Lifecycle<T>> getLifecycleClassFor(final Class<T> entityClass) {
		Class<Lifecycle<T>> lifecycleClass = getLifecycleConfiguration().getCachedLifecycleClassFor(entityClass);
		if (lifecycleClass == null) {
			lifecycleClass = getLifecycleClassCandidate(entityClass, getLifecycleNameFor(entityClass));
			getLifecycleConfiguration().cachedLifecycleClassFor(entityClass, lifecycleClass);
		}
		return lifecycleClass;
	}

	@SuppressWarnings("unchecked")
	private <T extends Entity> Class<Lifecycle<T>> getLifecycleClassCandidate(final Class<T> entityClass, final String lifecycleClassName) {
		Class<Lifecycle<T>> implClass = null;
		try {
			implClass = (Class<Lifecycle<T>>) Class.forName(lifecycleClassName);
		} catch (final ClassNotFoundException ex) {
			final LifecycleNotFoundException e = new LifecycleNotFoundException(entityClass, lifecycleClassName, ex);
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.entity.lifecycle.LifecycleFactory", "getLifecycleClassCandidate", e);
			throw e;
		}
		return implClass;
	}

	private <T extends Entity> String getLifecycleNameFor(final Class<T> entityClass) {
		final Package entityPackage = entityClass.getPackage();
		final String lifecyclePackageName = entityPackage.getName();
		return String.format("%s.%sLifecycle", lifecyclePackageName, entityClass.getSimpleName());
	}

	/**
	 * <p>
	 * プロパティ<code>lifecycleConfiguration</code>の値を取得する。
	 * </p>
	 * 
	 * @return プロパティ<code>lifecycleConfiguration</code>の値
	 */
	private LifecycleConfiguration getLifecycleConfiguration() {
		return lifecycleConfiguration;
	}

	/**
	 * <p>
	 * プロパティ<code>lifecycleContext</code>の値を取得する。
	 * </p>
	 * 
	 * @return プロパティ<code>lifecycleContext</code>の値
	 */
	private LifecycleContext getLifecycleContext() {
		return lifecycleContext;
	}

	/**
	 * <p>
	 * プロパティ<code>instanceCache</code>の値を取得する。
	 * </p>
	 * 
	 * @return プロパティ<code>instanceCache</code>の値
	 */
	private Map<Class<? extends Lifecycle<?>>, Lifecycle<?>> getInstanceCache() {
		return instanceCache;
	}

	private final LifecycleConfiguration lifecycleConfiguration = LifecycleConfiguration.getInstance();
	private final LifecycleContext lifecycleContext;
	private final Map<Class<? extends Lifecycle<?>>, Lifecycle<?>> instanceCache = new HashMap<Class<? extends Lifecycle<?>>, Lifecycle<?>>();

}
